<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>器灵2D</title>
</head>
<body>
    <div style="width: 1000px;height: 600px;position: relative;">
        <canvas id="canvasId" width="1000" height="600" style="border: 1px solid gold;position: absolute;"></canvas>
        <canvas id="bgCanvasId" width="1000" height="600" style="border: 1px solid gold;position: absolute;z-index: 1;pointer-events: none;"></canvas>
    </div>
    <div>
        <div>
            <input id="checkbox0" type="checkbox" disabled/>
            <input id="checkbox1" type="checkbox" />
            <input id="checkbox2" type="checkbox" disabled/><br/>
            <input id="checkbox7" type="checkbox" />
            <input id="checkbox8" type="checkbox" disabled/>
            <input id="checkbox3" type="checkbox" /><br/>
            <input id="checkbox6" type="checkbox" disabled/>
            <input id="checkbox5" type="checkbox" />
            <input id="checkbox4" type="checkbox" disabled/>
        </div>
        <span style="color: rgb(175, 175, 175);">点击屏幕会生成点</span>
        <button id="clearButton">清空</button>
    </div>

<script>
    //获得 2d 上下文对象
    var canvas = document.getElementById('canvasId');
    var bgCanvas = document.getElementById('bgCanvasId');
    var checkbox0 = document.getElementById('checkbox0');
    var checkbox1 = document.getElementById('checkbox1');
    var checkbox2 = document.getElementById('checkbox2');
    var checkbox3 = document.getElementById('checkbox3');
    var checkbox4 = document.getElementById('checkbox4');
    var checkbox5 = document.getElementById('checkbox5');
    var checkbox6 = document.getElementById('checkbox6');
    var checkbox7 = document.getElementById('checkbox7');
    var checkbox8 = document.getElementById('checkbox8');
    var ctx = canvas.getContext('2d');
    var bgCtx = bgCanvas.getContext('2d');
    // 参数
    var QL = {};                // 方法都绑定到这个上面
    class MyWorld {     // 世界地图
        static cellWidth = 32;         // 每个小格的宽度
        static pointWidth = 32;        // 每个小球的大小
        static 地图宽度 = canvas.width; // 最大宽度
        static 地图高度 = canvas.height; // 最大高度
    }

    //绘制矩形
    QL.画矩形 = function(rgba, x, y){
        ctx.fillStyle = rgba;
        let p = MyWorld.pointWidth / 2;
        ctx.fillRect(x-p, y-p, MyWorld.pointWidth, MyWorld.pointWidth);
    }

    // 画圆
    QL.画圆 = function(x, y, 实心吗) {
        ctx.fillStyle = ctx.strokeStyle = "black";
        let r=3, startAngle = 0, endAngle = Math.PI*2;
        ctx.beginPath();
        ctx.arc(x, y, r, startAngle, endAngle)
        ctx[实心吗? "fill" : "stroke"]();
    }

    // 画背景
    QL.画背景 = function(){
        bgCtx.beginPath();
        bgCtx.strokeStyle = `rgba(0,0,0,0.1)`;
        bgCtx.lineWidth = 1;
        for (let i = 0; i <= canvas.width; i+=MyWorld.cellWidth) {
            bgCtx.moveTo(i, 0);
            bgCtx.lineTo(i, canvas.height);
        }
        for (let i = 0; i <= canvas.height; i+=MyWorld.cellWidth) {
            bgCtx.moveTo(0, i);
            bgCtx.lineTo(canvas.width, i);
        }
        bgCtx.stroke()
    }

    QL.画背景();

    // 一个小点
    class Point {
        static 每格均分多少份 = 32;         // 一格的长度
        static 距离的缩放倍数 = 76;         // 计算力的时候
        static 加速度的缩放倍数 = 8;        // 加速度的影响程度
        static 最大速度 = Math.min(Point.每格均分多少份, 32);   // 速度最多 
        static 影响的距离 = 128;            // 只算128距离内的
        static 速度衰减程度 = 0.96;         //  每次刷新速度都会衰减
        static 点的最大质量 = 16;           // 点的质量最大多少
        static allPoints = [];           // 所有的点
        // 使用 1/x^3 - 1/x^2 困难重重，需要自定义规则来
        constructor(x, y, color, t, w){
            this.x = x;
            this.y = y;
            this.color = color||`rgba(0,0,0,0.1)`;
            this.t = t||0; // 粒子类型
            this.w = w||1; // 粒子重量
            this.vx = 0;
            this.vy = 0;
        }
        // 限制范围
        static clamp(v, max) {
            return Math.sign(v) * Math.min(Math.abs(v), max);
        }
        draw() {
            QL.画矩形(this.color, this.x, this.y);
            let d = 10;
            QL.画圆(this.x, this.y-d, this.t & 0b1000);
            QL.画圆(this.x+d, this.y, this.t & 0b100);
            QL.画圆(this.x, this.y+d, this.t & 0b10);
            QL.画圆(this.x-d, this.y, this.t & 0b1);
        }
        // 1. 计算速度；加速度由位置来决定
        calc() {
            Point.allPoints.forEach( anoterPoint => {
                let {x:x2, y:y2, vx:vx2, vy:vy2, w:w2} = anoterPoint;
                let dx = x2 - this.x;
                let dy = y2 - this.y;
                let dxy = Math.sqrt(dx**2 + dy**2);
                if( Math.abs(dxy) > Point.影响的距离 || dxy == 0 ) {
                    return;
                }
                let newDxy = dxy / Point.距离的缩放倍数;
                let axy = dxy===0 ? 0 : Math.sign(newDxy) * ( 1/(Math.abs(newDxy)**3) - 1/(Math.abs(newDxy)**2));
                axy *= w2;
                axy /= Point.加速度的缩放倍数;
                let ax = axy * (dx / Math.abs(dxy));
                let ay = axy * (dy / Math.abs(dxy));
                this.vy -= ay;
                this.vy = Point.clamp(this.vy*Point.速度衰减程度, Point.最大速度);
                this.vx -= ax;
                this.vx = Point.clamp(this.vx*Point.速度衰减程度, Point.最大速度);

                // console.log(`dxy=${dxy}, axy=${axy}, ax=${ax}, ay=${ay}, vx=${this.vx}, vy=${this.vy}`);
            });
        }
        // 2. 按照速度值移动
        move() {
            this.x += this.vx;
            this.y += this.vy;
            this.x = (this.x + MyWorld.地图宽度) % MyWorld.地图宽度;
            this.y = (this.y + MyWorld.地图高度) % MyWorld.地图高度;
        }
    }
    // console.log(Point.aArr);

    QL.画背景();
    const p1 = new Point(150, 50);
    const p2 = new Point(200, 60);
    Point.allPoints.push(p1);
    Point.allPoints.push(p2);

    // // 添加一些点
    for(let i=0; i<30; i++){
        const p = new Point(Math.random()*MyWorld.地图宽度, Math.random()*MyWorld.地图高度, null, 
                Math.random()*16, Math.random()*Point.点的最大质量);
        Point.allPoints.push(p);
    }

    document.getElementById("clearButton").onclick = e=>Point.allPoints=[];
    // 2022-02-05

    // 点击屏幕加一个点
    canvas.onclick = e => {
        let t = (checkbox1.checked<<3) | (checkbox3.checked<<2) | (checkbox5.checked<<1) | (checkbox7.checked<<0);
        let color = `rgba(${Math.random()*256},${Math.random()*256},${Math.random()*256},${Math.max(Math.random(),0.1)})`;
        let w = Math.random() * Point.点的最大质量;
        let p = new Point(e.offsetX,e.offsetY,color,t,w);
        Point.allPoints.push(p);
        console.info("增加点",p);
    };

    var intervalId = setInterval(() => {
        ctx.fillStyle = 'rgba(255,255,255,0.06)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        // QL.画背景();
        Point.allPoints.forEach(point => point.calc());
        Point.allPoints.forEach(point => point.move());
        Point.allPoints.forEach(point => point.draw());
        // clearInterval(intervalId);
    }, 10);



// todo 重量，分格子
// todo 参考共价键；链条，刚体，有机的结构
// todo 尝试量化距离，改为查表
// todo 类似于搭积木？


</script>

    <div style="margin-top: 20px;">
        <a href="https://www.runoob.com/w3cnote/html5-canvas-intro.html" target="_blank">canvas 菜鸟教程</a>
    </div>
</body>
</html>